﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using TSU.FAMC.ComputerEngineering.Classes.Enums;

namespace TSU.FAMC.ComputerEngineering.Classes.BooleanMaths.Fundamental
{
    /// <summary>
    /// Cube model in Boolean Mathematics. Provides with additional functionality for TernaryVector.
    /// </summary>
    public class Cube : TernaryVector
    {
        /// <summary>
        /// Default constructor.
        /// </summary>
        public Cube()
        {
        }

        /// <summary>
        /// Copy constructor.
        /// </summary>
        /// <param name="cube">Cube to copy values from.</param>
        public Cube(Cube cube) : base(cube as TernaryVector)
        {
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="dimension">Cube dimension (length of ternary vector).</param>
        public Cube(int dimension) : base(dimension)
        {
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="values">String values '0'/'1'/'-' to convert to ternary bits.</param>
        public Cube(string values) : base(values)
        {
        }

        /// <summary>
        /// Clones cube object.
        /// </summary>
        /// <returns>Object's clone.</returns>
        public new Object Clone()
        {
            var clone = new Cube(this);
            return clone;
        }

        /// <summary>
        /// Cube dimension (ternary vector length).
        /// </summary>
        public int Dimension
        {
            get { return Length; }
            set { Length = value; }
        }

        /// <summary>
        /// Initializes cube with specified value.
        /// </summary>
        /// <param name="value">Boolean value to init.</param>
        public void Init(BooleanValue value)
        {
            for (var i = 0; i < Dimension; i++)
            {
                this[i] = value;
            }
        }

        /// <summary>
        /// Initialized cube with another one (only specified bits will be used).
        /// </summary>
        /// <param name="cube">Cube to copy specified bits from.</param>
        public void Init(Cube cube)
        {
            if (Length != cube.Length)
            {
                throw new ArgumentException("Cubes are not compatible: cubes have different dimensions.");
            }

            for (var i = 0; i < Length; i++)
            {
                if (cube[i] == BooleanValue.Undefined)
                {
                    continue;
                }

                this[i] = cube[i];
            }
        }

        /// <summary>
        /// Builds intersection of two cubes as a cube.
        /// </summary>
        /// <param name="cube">Cube to intersect with.</param>
        /// <returns>Intersection result.</returns>
        public Cube Intersection(Cube cube)
        {
            if (Length != cube.Length)
            {
                throw new ArgumentException("Cubes are not compatible: cubes have different dimensions.");
            }

            if (IsOrthogonalTo(cube))
            {
                // orthogonal cubes
                return null;
            }

            var result = (Cube) Clone();
            for (var i = 0; i < Length; i++)
            {
                if (result[i] == BooleanValue.Undefined)
                {
                    result[i] = cube[i];
                }
            }

            return result;
        }

        /// <summary>
        /// Builds closure of two cubes as a cube (result of basic glue operation).
        /// </summary>
        /// <param name="cube">Cube to build closure with.</param>
        /// <returns>Cube as closure of two initial cubes.</returns>
        public Cube Closure(Cube cube)
        {
            if (Length != cube.Length)
            {
                throw new ArgumentException("Cubes are not compatible: cubes have different dimensions.");
            }
            
            var result = (Cube) Clone();
            for (var i = 0; i < Length; i++)
            {
                if (result[i] != cube[i])
                {
                    result[i] = BooleanValue.Undefined;
                }
            }

            return result;
        }

        /// <summary>
        /// Checkes whether cube is covered by the set of cubes.
        /// </summary>
        /// <param name="cover">Cube cover, consists of cubes.</param>
        /// <returns>True if cube is covered by the cover.</returns>
        public bool IsCoveredBy(CubeSet cover)
        {
            if (cover.Count == 0)
            {
                return false;
            }

            // checking dimensions
            var dimension = Length;
            if (cover.Any(cube => cube.Length != dimension))
            {
                throw new ArgumentException("Cube and cover must have the same dimension.");
            }

            // if cover contains cube it will be automatically covered
            if (cover.Contains(this))
            {
                return true;
            }


            // building not orthogonal cover
            var notOrthogonalCover = new CubeSet();
            foreach (var cube in cover)
            {
                if (IsOrthogonalTo(cube))
                {
                    continue;
                }

                // copying cube
                var notOrthogonalCube = (Cube) cube.Clone();

                // fixing determined variables
                for (var i = 0; i < Length; i++)
                {
                    if (this[i] == BooleanValue.Undefined)
                    {
                        continue;
                    }

                    notOrthogonalCube[i] = BooleanValue.Undefined;
                }

                // remembering cube
                notOrthogonalCover.Add(notOrthogonalCube);
            }

            if (notOrthogonalCover.Count == 0)
            {
                return false;
            }

            // checking whether Cover == 1
            if (notOrthogonalCover.Any(cube => cube.Rank == 0))
            {
                return true;
            }

            return notOrthogonalCover.IsFullSet;
        }

        /// <summary>
        /// Checks whether cube contains another one.
        /// </summary>
        /// <param name="cube">A cube to check whether it is a subset of the cube.</param>
        /// <returns>True if specified cube contains in this one.</returns>
        public bool Contains(Cube cube)
        {
            // calculating cubes intersection
            var intersection = Intersection(cube);

            // checking
            return intersection != null && intersection == cube;
        }


        /// <summary>
        /// Checkes whether cube intersects with some cube from set.
        /// </summary>
        /// <param name="set">Set of cubes to test.</param>
        /// <returns>True if cube intersects with a set of cubes.</returns>
        public bool TestIntersection(CubeSet set)
        {
            return set.Any(cube => !IsOrthogonalTo(cube));
        }


        /// <summary>
        /// Performs generalized glue operationon of two cubes.
        /// </summary>
        /// <param name="cube">Cube to glue with.</param>
        /// <returns>Result of generalized glue operation.</returns>
        public Cube GlueGeneralized(Cube cube)
        {
            if (Length != cube.Length)
            {
                throw new ArgumentException("Cubes are not compatible: cubes have different dimensions.");
            }

            if (Distance(cube) != 1)
            {
                // cubes must be 1-bit orthogonal.
                return null;
            }

            // searching for orthogonal bits
            Cube result = null;
            for (var i = 0; i < Length; i++)
            {
                if (!(this[i] != BooleanValue.Undefined && cube[i] != BooleanValue.Undefined &&
                    this[i] != cube[i]))
                {
                    // not orthogonal
                    continue;
                }

                // orthogonal bit
                // building product of glue operation
                result = new Cube(this);
                result.Init(cube);
                result[i] = BooleanValue.Undefined; // reseting orthogonal bit
                break;
            }

            return result;
        }

        /// <summary>
        /// Shifts bits on cube cyclically.
        /// If subset of bits specified, shift operation will use only subset bits.
        /// </summary>
        /// <param name="direction">Direction of shift.</param>
        /// <param name="bitsSubset">Bits to shift.</param>
        /// <returns>Shifted cube.</returns>
        public Cube Shift(int direction, List<int> bitsSubset = null)
        {
            if (direction == 0)
            {
                return new Cube(this);
            }

            var result = new Cube(this);

            if (bitsSubset != null)
            {
                foreach (var bit in bitsSubset)
                {
                    if (bit < 0 || bit >= Dimension)
                    {
                        throw new IndexOutOfRangeException(string.Format("Invalid bit index: {0} for cube {1}", bit, this));
                    }
                }

                // ordering and excluding repeating indices
                bitsSubset = bitsSubset.OrderBy(i => i).Distinct().ToList();
                var subCube = new Cube(bitsSubset.Count);
                for (var i = 0; i < subCube.Dimension; i++)
                {
                    subCube[i] = this[bitsSubset[i]];
                }

                // shifting subcube
                var shiftedSubcube = subCube.Shift(direction);

                // building resulting cube
                for (var i  = 0; i < subCube.Dimension; i++)
                {
                    result[bitsSubset[i]] = shiftedSubcube[i];
                }

                return result;
            }


            // shifting whole cube

            // projecting direction to dimension
            direction = direction % Dimension;
            
            for (var i = 0; i < Dimension; i++)
            {
                var shiftedBitIndex = i + direction;
                if (shiftedBitIndex < 0)
                {
                    shiftedBitIndex += Dimension;
                }
                else if (shiftedBitIndex >= Dimension)
                {
                    shiftedBitIndex -= Dimension;
                }

                result[shiftedBitIndex] = this[i];
            }

            return result;
        }
    }
}
